package view;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.Observable;
import java.util.Observer;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.PlainDocument;
import model.Model_Config;
import model.Model_Image_Line;
import model.Model_Image_Original;
import Enums.Notifier;
import Enums.State;
import controller.Actions;
import controller.Controller;
import java.awt.AlphaComposite;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import javax.swing.JCheckBox;
import javax.swing.JMenuItem;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.border.TitledBorder;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

public class GUI extends JFrame implements Observer, WindowListener {

    public GUI() {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                initGui();
                initData();
            }
        });
    }

    private void initData() {

        model_config = new Model_Config();
        model_log = new PlainDocument();
        model_image_original = new Model_Image_Original();
        model_image_line = new Model_Image_Line(model_config, model_image_original);
        controller = Controller.getInstance();
        controller.setImage_Line(model_image_line);
        controller.setImage_Original(model_image_original);
        controller.createControllerFitness();
        controller.setConfig(model_config);
        controller.setGUI(this);
        controller.setChart(((ChartPanel) panel_chart).getChart());

        setModel_config(model_config);
        setModel_image_line(model_image_line);
        setModel_image_original(model_image_original);
        setController(controller);
        setModel_log(model_log);

    }

    @Override
    public void update(Observable o, Object arg) {
        Notifier n = (Notifier) arg;
        switch (n) {
            case OriginalImage:
                panel_imageOriginal.repaint();
                break;
            case LineImage:
                panel_ausgabe.repaint();
                break;
        }
    }
    /**************************************************************************
     *                                                                        *
     *                              Models                                    *
     *                                                                        *
     ***************************************************************************/
    private Model_Config model_config;
    private PlainDocument model_log;
    private Model_Image_Line model_image_line;
    private Model_Image_Original model_image_original;
    private Image original_image_resized;
    private Image line_image;
    private Controller controller;
    private boolean background = false;
    private float trans = 0.3f;
    private AlphaComposite alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, trans);
    private Graphics2D g2d;

    public void setController(Controller controller) {
        this.controller = controller;
        setListener();
    }

    public void setLine_image(Image line_image) {
        this.line_image = line_image;
    }

    public void changeModelConfig(Model_Config config) {
        spinner_imagesPerGeneration.setValue(config.getImagesPerGeneration());
        spinner_linesPerImage.setValue(config.getLinesPerImage());
    }

    public void setModel_config(Model_Config _config) {
        this.model_config = _config;
        comboBox_scaleHint.setModel(model_config.createMyComboboxModel_scale());
        spinner_imagesPerGeneration.setModel(model_config.createMySpinnerModel_imagesPerGeneration(15, 1, Integer.MAX_VALUE, 5));
        spinner_linesPerImage.setModel(model_config.createMySpinnerModel_linesPerImage(300, 1, Integer.MAX_VALUE, 25));
    }

    public void setModel_image_line(Model_Image_Line model_image_line) {
        this.model_image_line = model_image_line;
    }

    public void setModel_image_original(Model_Image_Original model_image_original) {
        this.model_image_original = model_image_original;
    }

    public void setModel_log(PlainDocument model_log) {
        this.model_log = model_log;
        logArea.setDocument(this.model_log);
        controller.setModelLog(this.model_log);
    }
    /**************************************************************************
     *                                                                        *
     *                          GUI ELEMENTE                                  *
     *                                                                        *
     ***************************************************************************/

    /* graphic components */
    private JToolBar toolbar;
    private JButton button_loadImage, button_storeImage, button_loadProcess, button_saveProcess, button_exit, button_startProcess;
    private JComboBox comboBox_scaleHint;
    private Image backgroundImage;
    private JSpinner spinner_imagesPerGeneration, spinner_linesPerImage;
    private JTextArea logArea;
    private JMenuItem mi;
    private JPopupMenu popupMenu;
    private JPanel panel_ausgabe, panel_left, panel_leftcenter, panel_imageOriginal, panel_config, panel_chart;
    private JSplitPane splitLeftCenter, splitChart, split;
    /* layout fuer die configs */
    private GridBagLayout gridbaglayout = new GridBagLayout();
    private GridBagConstraints constraints = new GridBagConstraints();
    private ImageIcon[] icon_startButton = new ImageIcon[2];
    private int currentIcon = 0; // icon index, welches grade aktuell ist
    // zum Tooltip setzen
    protected final String htmlOpen = "<html><body>", htmlEnd = "</body></html>", htmlNl = "<br>";
    Font font = new Font(null, Font.PLAIN, 12);
    // steuerung der Tooltips erzeugen und hinzufügen
    private final JCheckBox visTooltip = new JCheckBox("Tooltip", true),
            continuousLayout = new JCheckBox("Echtzeit", true);
    private final JSlider delayTooltip = new JSlider(0, 4000, 120), transparency = new JSlider(0, 100);
    private JPanel logimage = new JPanel();
    private JSpinner logimageinterval = new JSpinner(new SpinnerNumberModel(1000, 10, 100000, 200));
    private JCheckBox logimageactivate = new JCheckBox(Actions.logimage);
    private JTextField logname = new JTextField("logs");

    private void initGui() {
        Toolkit tk = Toolkit.getDefaultToolkit();
        // das Fenster auf x% des Monitors setzen
        Dimension dim_frame = new Dimension((int) (tk.getScreenSize().width * 0.75), (int) (tk.getScreenSize().height * 0.75));
        // start/pause button icons
        icon_startButton[0] = new ImageIcon(getClass().getResource("/icons/Play24.gif"));
        icon_startButton[1] = new ImageIcon(getClass().getResource("/icons/Pause24.gif"));
        // toolbar + buttons
        button_loadImage = new JButton(new ImageIcon(getClass().getResource("/icons/scanner.png")));
        button_loadImage.setToolTipText("Bild laden");
        button_storeImage = new JButton(new ImageIcon(getClass().getResource("/icons/Print24.gif")));
        button_storeImage.setToolTipText("das aktuelle Bild speichern");
        button_loadProcess = new JButton(new ImageIcon(getClass().getResource("/icons/Open24.gif")));
        button_loadProcess.setToolTipText("Prozessfortschritt laden");
        button_saveProcess = new JButton(new ImageIcon(getClass().getResource("/icons/Save24.gif")));
        button_saveProcess.setToolTipText("Prozessfortschritt speichern");
        button_startProcess = new JButton(icon_startButton[currentIcon]);
        button_startProcess.setToolTipText("Prozess starten / anhalten");
        button_exit = new JButton(new ImageIcon(getClass().getResource("/icons/leave24.gif")));
        button_exit.setToolTipText("Programm beenden");

        toolbar = new JToolBar(JToolBar.HORIZONTAL);
        toolbar.setOpaque(false);
        Dimension sepDim = new Dimension(3, 0);
        toolbar.setFloatable(false);
        toolbar.add(button_loadImage);
        toolbar.add(button_storeImage);
        toolbar.addSeparator(sepDim);
        toolbar.add(button_startProcess);
        toolbar.addSeparator(sepDim);
        toolbar.add(button_loadProcess);
        toolbar.add(button_saveProcess);
        toolbar.addSeparator(sepDim);
        toolbar.add(button_exit);

        // panel left center
        panel_leftcenter = new JPanel(new GridLayout(2, 1, 5, 5));
        // tabbed pane
        logArea = new JTextArea();
        logArea.setLineWrap(true);
        logArea.setEditable(false);


        panel_config = new JPanel(gridbaglayout);
        // JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
        JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);

        // panel right, und panel image original
        initImagePanels();
        tabbedPane.addTab("Bild", panel_imageOriginal);
        JScrollPane scroll_config = new JScrollPane(panel_config, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        tabbedPane.addTab("Einstellungen", new ImageIcon(getClass().getResource("/icons/Properties16.gif")), scroll_config);

        // gridbaglayout festlegen
        constraints.fill = GridBagConstraints.HORIZONTAL;
        gridbaglayout.setConstraints(scroll_config, constraints);
        //constraints.weightx = 100;

        // die erste opt erste zeile erste spalte
        constraints.gridx = 0;
        constraints.gridy = 0;

        comboBox_scaleHint = new JComboBox();
        spinner_imagesPerGeneration = new JSpinner();
        spinner_linesPerImage = new JSpinner();

        addConfig("Strichanzahl", "setzt die Anzahl der \b Striche pro Bild", spinner_linesPerImage);
        addConfig("Bilderanzahl", "setzt die Anzahl der Bilder pro Generation", spinner_imagesPerGeneration);
        addConfig("Transparenz", "setzt die Transparenz des Originalbildes\b (Klick auf das Strichbild)", transparency);        
        logimage.setLayout(new GridLayout(1, 2));
        logimage.add(logimageactivate);
        logimage.add(logimageinterval);
        addConfig("Bilder-Log", "speichert im angegebenen\bInterval das aktuelle Bild", logimage);
        addConfig("Logname", "hier Name für den Log-Ordner vergeben\b<span style='color:red;'>Wichtig: die Ordnernamenkonvention des\bjeweiligen Betriebssystems beachten!</span>", logname);
        addConfig("Skaliermethode", "wählt die Art wie das Strichbild skaliert werden soll", comboBox_scaleHint);
        addConfig("Tooltip", "schaltet Tooltips ein/aus", visTooltip);
        addConfig("Verzögerung", "Verzögerung bis die Tooltips erscheinen", delayTooltip);
        addConfig("Echtzeit", "Inhalt der Einzelflächen beim \b Ziehen ändern", continuousLayout);

        // panel left
        panel_left = new JPanel(new BorderLayout());
        // panel_left.setPreferredSize(new Dimension(250, 400));

        panel_left.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        panel_left.add(toolbar, BorderLayout.NORTH);
        panel_left.add(panel_leftcenter, BorderLayout.CENTER);

        // frame
        splitLeftCenter = new JSplitPane(JSplitPane.VERTICAL_SPLIT, tabbedPane, new JScrollPane(logArea));
        panel_left.add(splitLeftCenter, BorderLayout.CENTER);
        splitLeftCenter.setOneTouchExpandable(true);

        splitChart = new JSplitPane(JSplitPane.VERTICAL_SPLIT, panel_ausgabe, panel_chart);
        splitChart.setDividerLocation((int) (dim_frame.getHeight() * 0.6));
        splitChart.setOneTouchExpandable(true);

        split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, panel_left, splitChart);
        add(split);

        popupMenu = new JPopupMenu();
        mi = new JMenuItem("Verlauf löschen");
        mi.setActionCommand(Actions.logreset);
        mi.setIcon(new ImageIcon(getClass().getResource("/icons/Delete16.gif")));
        popupMenu.add(mi);

        /**********************************************************************
        interne Listener
         **********************************************************************/
        logArea.setComponentPopupMenu(popupMenu);
        addWindowListener(this);
        // tooltip ein-, oder ausschalten
        visTooltip.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                setTooltipOptions();
            }
        });
        // tooltip anzeige verzögerung
        delayTooltip.addChangeListener(new ChangeListener() {

            public void stateChanged(ChangeEvent e) {
                setTooltipOptions();
            }
        });

        continuousLayout.addChangeListener(new ChangeListener() {

            public void stateChanged(ChangeEvent e) {
                setContinuousLayout(continuousLayout.isSelected());
            }
        });

        transparency.addChangeListener(new ChangeListener() {

            public void stateChanged(ChangeEvent e) {
                transparency();
                panel_ausgabe.repaint();
            }
        });

        setContinuousLayout(continuousLayout.isSelected());
        setTooltipOptions();
        updateButtons(State.nothing);
        /******************************** end *********************************/
        setTitle("Genetische Kunst");
        setPreferredSize(dim_frame);
        pack();
        setLocationRelativeTo(null);
        setVisible(false);
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
    }

    private void initImagePanels() {
        XYSeriesCollection seriesColl = new XYSeriesCollection(new XYSeries(""));
        JFreeChart chart = ChartFactory.createXYLineChart("", "Generationen", "Fitnesswert",
                seriesColl, PlotOrientation.VERTICAL, false, false, false);

        panel_chart = new ChartPanel(chart);

        panel_ausgabe = new JPanel() {

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                g2d = (Graphics2D) g;
                if (model_image_line.getFittestImage() != null) {
                    line_image = model_image_line.getResizedImage(this.getWidth(), this.getHeight(), model_config.getScaleHint());
                    int dx = Math.abs((line_image.getWidth(null) - this.getWidth()) / 2);
                    int dy = Math.abs((line_image.getHeight(null) - this.getHeight()) / 2);
                    if (background) {
                        backgroundImage = model_image_original.getImage_original();
                        if(backgroundImage.getWidth(null)>this.getWidth()){
                            backgroundImage = backgroundImage.getScaledInstance(this.getWidth(), -1, BufferedImage.SCALE_FAST);
                        }
                        if(backgroundImage.getHeight(null)>this.getHeight()){
                            backgroundImage = backgroundImage.getScaledInstance(-1, this.getHeight(), BufferedImage.SCALE_FAST);
                        }
                        g2d.drawImage(backgroundImage, dx, dy, null);
                        g2d.setComposite(alphaComposite);
                    }
                    g.drawImage(line_image, dx, dy, null);
                } else {
                    clearPanel(this);
                }
            }
        };
        panel_ausgabe.addMouseListener(new MouseListener() {

            public void mouseClicked(MouseEvent e) {
                background = !background;
                panel_ausgabe.repaint();
            }

            public void mousePressed(MouseEvent e) {
            }

            public void mouseReleased(MouseEvent e) {
            }

            public void mouseEntered(MouseEvent e) {
            }

            public void mouseExited(MouseEvent e) {
            }
        });

        panel_imageOriginal = new JPanel() {

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                //clearPanel(this);
                if (model_image_original.getImage_original() != null) {
                    original_image_resized = model_image_original.getImage_resized(this.getWidth(), this.getHeight());
                    int dxO = Math.abs((original_image_resized.getWidth(null) - this.getWidth()) / 2);
                    int dyO = Math.abs((original_image_resized.getHeight(null) - this.getHeight()) / 2);
                    g.drawImage(original_image_resized, dxO, dyO, null);
                } else {
                    clearPanel(this);
                }
            }
        };
        panel_imageOriginal.setToolTipText(createTooltip("um zu gewichten\bauf das Bild doppelklicken"));
        panel_imageOriginal.addMouseListener(new MouseListener() {

            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    if (model_image_original.getImage_original() != null) {
                        weightImage();
                    }
                }
            }

            public void mousePressed(MouseEvent e) {
            }

            public void mouseReleased(MouseEvent e) {
            }

            public void mouseEntered(MouseEvent e) {
            }

            public void mouseExited(MouseEvent e) {
            }
        });
    }

    // weist den Komponenten die Actions zu
    private void setListener() {
        // listener zuweisen
        button_loadImage.addActionListener(controller);
        button_storeImage.addActionListener(controller);
        button_loadProcess.addActionListener(controller);
        button_saveProcess.addActionListener(controller);
        button_exit.addActionListener(controller);
        button_startProcess.addActionListener(controller);
        mi.addActionListener(controller);

        //actioncommands setzen
        button_loadImage.setActionCommand(Actions.laden);
        button_storeImage.setActionCommand(Actions.storeImage);
        button_loadProcess.setActionCommand(Actions.oeffnen);
        button_saveProcess.setActionCommand(Actions.speichern);
        button_exit.setActionCommand(Actions.beenden);
        button_startProcess.setActionCommand(Actions.start);
        mi.setActionCommand(Actions.logreset);

        model_image_original.addObserver(this);
        model_image_line.addObserver(this);
     
        logimageactivate.addActionListener(controller);
        logimageinterval.addChangeListener(controller);
    }

    /**
     * Von Controller wird den Status genommen
     * und je nach status werden die
     * buttons enable-false oder enable-true gesetzt
     */
    public void updateButtons(State state) {
        switch (state) {
            case nothing: {
                button_loadImage.setEnabled(true);
                button_loadProcess.setEnabled(true);
                button_saveProcess.setEnabled(false);
                button_storeImage.setEnabled(false);
                button_startProcess.setEnabled(false);
                spinner_linesPerImage.setEnabled(true);
                spinner_imagesPerGeneration.setEnabled(true);
                break;
            }
            case ready: {
                button_loadImage.setEnabled(true);
                button_loadProcess.setEnabled(true);
                button_saveProcess.setEnabled(false);
                button_storeImage.setEnabled(false);
                button_startProcess.setEnabled(true);
                spinner_linesPerImage.setEnabled(true);
                spinner_imagesPerGeneration.setEnabled(true);
                break;
            }
            case running: {
                button_loadImage.setEnabled(false);
                button_loadProcess.setEnabled(false);
                button_saveProcess.setEnabled(true);
                button_storeImage.setEnabled(true);
                button_startProcess.setEnabled(true);
                spinner_linesPerImage.setEnabled(false);
                spinner_imagesPerGeneration.setEnabled(false);
                setIconPause();
                break;
            }
            case waiting: {
                button_loadImage.setEnabled(true);
                button_loadProcess.setEnabled(true);
                button_saveProcess.setEnabled(true);
                button_storeImage.setEnabled(true);
                button_startProcess.setEnabled(true);
                spinner_linesPerImage.setEnabled(false);
                spinner_imagesPerGeneration.setEnabled(false);
                setIconStart();
                break;
            }
        }

    }

    private void setIconPause() {
        button_startProcess.setIcon(icon_startButton[1]);
    }

    private void setIconStart() {
        button_startProcess.setIcon(icon_startButton[0]);
    }

    /**
     * macht die zeichenfläche wieder 'sauber'
     * @param panel - JPanel das 'sauber gewischt' werden soll
     */
    protected void clearPanel(JPanel panel) {
        Graphics g = panel_ausgabe.getGraphics();
        Color c = g.getColor();
        g.setColor(new Color(240, 240, 240));
        g.fillRect(0, 0, panel_ausgabe.getWidth(), panel_ausgabe.getHeight());
        g.setColor(c);
    }

    public void clearPanelOriginal() {
        clearPanel(panel_imageOriginal);
    }

    /**
     * neue Steuerungs Elemente hinzufügen
     * @param name - Wie soll das Element heißen
     * @param description - eine Beschreibung der Komponente, wird als Tooltip angezeigt,
     *                      wenn die Beschreibung als Mehrzeiliger Tooltip erscheinen soll, die Zeilen mit '\b' trennen
     * @param config - Steuerelement
     */
    protected void addConfig(String name, String description, JComponent config) {
        config.setToolTipText(createTooltip(description));
        config.setBorder(new TitledBorder(BorderFactory.createLineBorder(Color.black), name, TitledBorder.LEFT, TitledBorder.TOP, font, Color.blue));
        panel_config.add(config, constraints);
        // zeile für nächste comp erhöhen
        constraints.gridy++;
    }

    /**
     * erzeugt einen Tooltip mit Zeilenumbruch an angebebener Stelle
     * Falls kein Zeilenumbruch erwünscht, \b weg lassen
     * zB: "Das ist ein Langer Tooltip" - erscheint als "Das ist ein Langer Tooltip"
     * zB: "Das ist ein Langer \b Tooltip" - erscheint als "Das ist ein Langer
     *                                                      Tooltip"
     * @param tooltip - der Text der erscheinen soll
     */
    private String createTooltip(String tooltip) {
        String[] t = tooltip.split("\b");
        String o = htmlOpen;
        for (String s : t) {
            o += s + htmlNl;
        }
        o += htmlEnd;
        return o;
    }

    private void weightImage() {
        new WeightingDialog(model_image_original, this, this.controller);
    }

    /**
     * regelt die Tooltips
     * @param delay - anzeigeverzögerung
     * @param visibility - anzeigen oder verbergen
     */
    private void setTooltipOptions() {
        ToolTipManager.sharedInstance().setInitialDelay(delayTooltip.getValue());
        ToolTipManager.sharedInstance().setEnabled(visTooltip.isSelected());
    }

    private void transparency() {
        alphaComposite = alphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) (transparency.getValue() / 100f));
    }

    // setzt das Layout auf 'continuous', dass die Panels 'live' beim ziehen der Splits gezeichnet werden
    private void setContinuousLayout(boolean continuous) {
        splitLeftCenter.setContinuousLayout(continuous);
        splitChart.setContinuousLayout(continuous);
        split.setContinuousLayout(continuous);
    }
    /*
    public static void main(String[] s){
    GUI2 g= new GUI2();
    g.setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
     */

    public void windowOpened(WindowEvent e) {
    }

    public void windowClosing(WindowEvent e) {
        button_exit.doClick();
    }

    public void windowClosed(WindowEvent e) {
        button_exit.doClick();
    }

    public void windowIconified(WindowEvent e) {
    }

    public void windowDeiconified(WindowEvent e) {
    }

    public void windowActivated(WindowEvent e) {
    }

    public void windowDeactivated(WindowEvent e) {
    }

    public String getLogName() {
        return logname.getText();
    }

    public void logname(boolean logimage) {
        logname.setEnabled(logimage);
    }
}
